#include <core.p4>
#include <v1model.p4>

const bit<16> ETH_TYPE_IP_V4 = 0x0800;
const bit<16> ETH_TYPE_SR = 0x1234;

#define MAX_SR_ENTRIES 9

typedef bit<9>  PortID_t;
typedef bit<48> MacAddress;
typedef bit<32> IPv4Address;

header L2_Frame {
    MacAddress destination;
    MacAddress source;
    bit<16>    type_id;
}

header SR_HopUnit {
    bit<1>     end_of_route;
    bit<15>    next_port_id;
}

header IPv4_Packet {
    bit<4>     version;
    bit<4>     hdr_len;
    bit<8>     diff_serv;
    bit<16>    total_len;
    bit<16>    pkt_id;
    bit<3>     flags;
    bit<13>    frag_offset;
    bit<8>     time_to_live;
    bit<8>     protocol_id;
    bit<16>    checksum;
    IPv4Address src_addr;
    IPv4Address dst_addr;
}

struct UserContext {
}

struct PacketHeaders {
    L2_Frame                   ether_hdr;
    SR_HopUnit[MAX_SR_ENTRIES] sr_hops;
    IPv4_Packet                ip_hdr;
}

parser IngressParser(packet_in p,
                     out PacketHeaders pkt_h,
                     inout UserContext u_meta,
                     inout standard_metadata_t std_meta) {

    header h_sr = pkt_h.sr_hops;

    state State_Begin {
        transition State_Parse_L2;
    }

    state State_Parse_L2 {
        p.extract(pkt_h.ether_hdr);
        transition select(pkt_h.ether_hdr.type_id) {
            ETH_TYPE_SR: State_Parse_SR;
            ETH_TYPE_IP_V4: State_Parse_IPv4;
            default: accept;
        }
    }

    state State_Parse_SR {
        p.extract(h_sr.next);
        transition select(h_sr.last.end_of_route) {
            1: State_Parse_IPv4;
            default: State_Parse_SR;
        }
    }

    state State_Parse_IPv4 {
        p.extract(pkt_h.ip_hdr);
        transition accept;
    }
}

control Verifier(inout PacketHeaders pkt_h, inout UserContext u_meta) {
    apply {  }
}

control ForwardingLogic(inout PacketHeaders pkt_h,
                        inout UserContext u_meta,
                        inout standard_metadata_t std_meta) {

    action Pkt_Drop() {
        mark_to_drop(std_meta);
    }

    action SR_NextHop() {
        std_meta.egress_spec = (PortID_t)pkt_h.sr_hops[0].next_port_id;
        pkt_h.sr_hops.pop_front(1);
        
        if (pkt_h.ip_hdr.isValid()) {
            pkt_h.ip_hdr.time_to_live = pkt_h.ip_hdr.time_to_live - 1;
        }
    }
    
    action SR_RouteFinish() {
        std_meta.egress_spec = (PortID_t)pkt_h.sr_hops[0].next_port_id;
        pkt_h.sr_hops.pop_front(1);
        pkt_h.ether_hdr.type_id = ETH_TYPE_IP_V4;
        
        if (pkt_h.ip_hdr.isValid()) {
            pkt_h.ip_hdr.time_to_live = pkt_h.ip_hdr.time_to_live - 1;
        }
    }

    action IPv4_Lookup_Fwd(PortID_t out_port) {
        std_meta.egress_spec = out_port;
        pkt_h.ip_hdr.time_to_live = pkt_h.ip_hdr.time_to_live - 1;
    }

    table IP_Routing_Table {
        key = {
            pkt_h.ip_hdr.dst_addr: exact;
        }
        actions = {
            IPv4_Lookup_Fwd;
            Pkt_Drop;
        }
        size = 1024;
        default_action = Pkt_Drop();
    }

    apply {
        if (pkt_h.sr_hops.isValid() && pkt_h.sr_hops[0].isValid()) {
            if (pkt_h.sr_hops[0].end_of_route == 1) {
                SR_RouteFinish();
            } else {
                SR_NextHop();
            }
        } 
        else if (pkt_h.ip_hdr.isValid()) {
            IP_Routing_Table.apply();
        } 
        else {
            Pkt_Drop();
        }
    }
}

control EgressFilter(inout PacketHeaders pkt_h,
                     inout UserContext u_meta,
                     inout standard_metadata_t std_meta) {
    apply {  }
}

control Checksum_Finalizer(inout PacketHeaders pkt_h, inout UserContext u_meta) {
    apply {  }
}

control FinalDeparser(packet_out p, in PacketHeaders pkt_h) {
    apply {
        p.emit(pkt_h.ether_hdr);
        p.emit(pkt_h.sr_hops);
        p.emit(pkt_h.ip_hdr);
    }
}

V1Switch(
    IngressParser(),
    Verifier(),
    ForwardingLogic(),
    EgressFilter(),
    Checksum_Finalizer(),
    FinalDeparser()
) main;